#include <stdio.h>

/**
 * 指针: 指针存放的是元素的地址或者元素（数组元素）的首地址
 *  1.32系统下指针大小为4字节，64位系统下指针大小为8字节
 *  2.指向int类型数据的指针就是int类型指针，指向char类型数据的指针就是char类型指针
 */

int main()
{
	/**
	 * 1.指针定义语法增强
	 * 	以下三种定义指针的语法都是正确的，推荐第三种，这样可以体现，指针变量就是一个普通变量，只不过它的类型是指针类型，值是内存地址而已
	 * 	int  *p;
	 * 	int * p;
	 * 	int*  p;
	 * 2.一次定义多个指针
	 *  int * foo, * bar;	//正确
	 *  int * foo, bar;		//错误
	 * 3.&和*在指针中的作用
	 *  &运算符是取地址，*运算符是解地址引用，&运算符与*运算符互为逆运算
	 */
    // 变量的声明
    int a = 10, b = 20;
    printf("a = %d, b = %d, &a = %p, &b = %p\n", a, b, &a, &b);
    int nums[] = { 1, 2, 3 };
    printf("---------------------------------------------\n");

    // 通过打印结果可以看出: 1.数组在内存中是连续存储的 2.一个int类型数据在内存中占据四个字节
    int length = sizeof(nums) / sizeof(nums[0]);
    for(int i = 0; i < length; i++)
    {
    	printf("nums[%d] = %d, &nums[%d] = %p\n", i, nums[i], i, &nums[i]);
    }
    printf("---------------------------------------------\n");

    // 指针变量的声明，一般初始时声明为空指针
    int* p = NULL;
    // 在指针变量中存储变量a的地址
    p = &a;
    // 注意: 1.指针p本身也会有一个物理内存地址 2.32位系统中指针占据4个字节，64位系统中指针占据8个字节
    printf("a的内存地址 = %p, 指针变量p中存储的(变量a)内存地址 = %p, 指针p的地址&p = %p\n", &a, p, &p);
    printf("指针大小:%d\n", sizeof(p));
    // *p代表的是a本身,把100赋值给*p,就意味把100赋值给a,*这个符号代表对一个指针类型数据解引用
    *p = 100;
    printf("a = %d, *p代表的变量的值(a的值) = %d\n", a, *p);
    p = &b;
    // 此时*p代表的是b本身,把100赋值给*p,就意味把100赋值给b
    *p = 200;
    printf("b = %d, *p代表的变量的值(b的值) = %d\n", b, *p);
    printf("---------------------------------------------\n");

    /**
     * 物理内存与寄存器
     */
    int num = 10;
    // 打印数字 10
    printf("%d\n", num);
    // 打印数字10的内存地址
    printf("%p\n", &num);
    // 为什么&num可以，&10就会报错，因为&代表取内存地址，num存放于内存中，而10存放在寄存器中
    //printf("%p\n", &10);
    printf("---------------------------------------------\n");

    /**
     * 在一行代码中定义两个指针变量
     */
    int c = 100, d = 200;
    // 一次性定义两个指针变量: 正确写法
    int* p1, * p2;
    // 一次性定义两个指针变量: 错误写法,这种写法p2不是指针类型变量,是int类型变量
    //int* p1,p2;
    p1 = &c;
    p2 = &d;
    printf("p1的物理地址 = %p\n", p1);
    printf("p2的物理地址 = %p\n", p2);
    printf("---------------------------------------------\n");

    /**
     * C语言中的NULL指针
     */
    int* p_null = NULL;
    printf("pNULL的内存地址 = %p\n", p_null);
    printf("---------------------------------------------\n");

    //C语言字符串和指针
    char stra[] = { "hello" };
    char strb[] = { "hello" };

    /**
     * 早期没有引入const关键字，所以声明指针类型的字符串变量时前面不加const关键字，这是历史遗留问题，按规范定义，指针要使用const关键字修饰
     */
    char* spa = "hello"; // 存放在只读数据区 .rdata
    // 最规范的定义
    //const char* spa = "hello"; // 存放在只读数据区 .rdata
    char* spb = "hello"; // 存放在只读数据区 .rdata
    // 最规范的定义
    //const char* spb = "hello"; // 存放在只读数据区 .rdata

    char* spc = { "hello" }; // 存放在只读数据区 .rdata
    // 最规范的定义
    //const char* spc = { "hello" }; // 存放在只读数据区 .rdata
    char* spd = { "hello" }; // 存放在只读数据区 .rdata
    // 最规范的定义
    //const char* spd = { "hello" }; // 存放在只读数据区 .rdata

    printf("%d \n", (stra == strb));
    printf("%d \n", (spa == spb));
    printf("%d \n", (spc == spd));
    printf("---------------------------------------------\n");

    // 数据的小端存储:高位数存放在高地址,低位数存放在低地址,变量的首地址就是变量的低地址
    // j的值 从右往左: 低位数->高位数(这是16进制,可以像理解10进制数据一样来理解,从右往左依次是 个位->十位->百位->千位,位数是从低到高)
    // 地址的值 从右往左: 高地址->低地址(因为地址是二进制数据,只是在显示时将4个二进制数据合并为一个16进制数据)
    int j = 0x12345678;
    int* pj = &j;
    printf("j的物理地址 = %p\n", pj);

    // 指针是一个常量
    int m =10, n = 20;
    int* mp = &m;
    int* np = &n;
    // 指针是一个常量，所以放开会报错
    //&m = &n;

    // 打印数组类型字符串
    char str1[] = "hello";
    printf("str1 = %s\n", str1);
    // 打印指针类型字符串
    char* str2 = "world";
    printf("str2 = %s\n", str2);
}
